This notebook explores, validates, and extracts key parameters from public mobility available from Google, Apple, and Descartes Labs.

Set up and pulling data

library(EnvCpt) # for change point detection
library(tidyverse)
library(lubridate)
google_mob <- read_csv(
  "https://www.gstatic.com/covid19/mobility/Global_Mobility_Report.csv",
  # col_types b/c read_csv detects logical in missing values in first 1000 rows.
  col_types = "ccccDnnnnnn", )
apple_mob <- read_csv(
  "https://covid19-static.cdn-apple.com/covid19-mobility-data/2006HotfixDev11/v1/en-us/applemobilitytrends-2020-04-20.csv")
descartes_mob <- read_csv(
  "https://raw.githubusercontent.com/descarteslabs/DL-COVID-19/master/DL-us-mobility-daterow.csv")

Data Exploration

Data summaries

There is some substantial differences in these data sets in their structure, but also on their content. We first tidy up the structure, then we dive into each set of metrics.

Tidy structure

Google:

(google_mob <- google_mob %>%
  gather(location_type, pct_change, contains("percent_change")) %>%
  mutate(location_type = str_remove(location_type,
                                    "_percent_change_from_baseline")))

Apple:

(apple_mob <- apple_mob %>%
   gather(date, routing_requests, -geo_type, -region, -transportation_type))

Descartes is already tidy.

Google

Google mobility measures “the percent change in visits to places like grocery stores and parks within a geographic area”. This is split out into six location types (retail/recreation, grocery/pharmacy, parks, transit stations, workplaces, and residential) and measure changes from a baseline date of Feb 15, 2020.

It would be interesting know how visits are counted. Do I need to leave a place to visit that place again? Does staying at home all day mean I visited home just once? These details are not revealed from the summary.

Top/Bottom 5 countries by cumulative change

The mobility documentation also warns: “Location accuracy and the understanding of categorized places varies from region to region, so we don’t recommend using this data to compare changes between countries, or between regions with different characteristics (e.g. rural versus urban areas).”

It’s a good caveat to think about - but we need to start somewhere:

google_mob %>%
  filter(is.na(sub_region_1) & is.na(sub_region_2)) %>%
  group_by(country_region, location_type) %>%
  mutate(sum_pct_change = sum(abs(pct_change))) %>%
  group_by(location_type) %>% 
  mutate(change_rank_top = dense_rank(sum_pct_change)) %>%
  mutate(change_rank_bot = dense_rank(-sum_pct_change)) %>%
  group_by(country_region) %>%
  filter(any(change_rank_top %in% 1:5 | change_rank_bot %in% 1:5)) %>%
  ggplot(aes(date, pct_change, color = country_region)) +
  facet_wrap(~ location_type, ncol = 2) +
  geom_line() -> p

plotly::ggplotly(p)

It appears the countries that have changed the least are Benin, Japan, Taiwan, Tanzania, and Yemen. Those that changed the most are Bolivia, Ecuador, Italy, Mauritius, and Spain. In general there is a consistent decline in all categories except parks and residential. That would more or less align with what we would expect from a stay-at-home order with some outdoor exceptions. Denmark really loves their parks.

Location aggregates

google_mob %>% group_by(country_region) %>%
  summarise(distinct_sr1 = n_distinct(sub_region_1, na.rm = T),
            distinct_sr2 = n_distinct(sub_region_2, na.rm = T)) %>%
  arrange(desc(distinct_sr2), desc(distinct_sr1))

The Google mobility data contains two sub-regions for the US (states and counties) and one sub-region for 49 other countries. The remaining 82 countries do not have any sub-regional information.

Apple

Apple defines their measure as “a relative volume of directions requests per country/region or city compared to a baseline volume on January 13th, 2020”. So this starts earlier than the Google data and cuts their data by transportation type (driving, walking, or transit). Perhaps the transit direction requests could be comparable to Google’s.

Additionally they define their days as midnight to midnight, Pacific time and point out that there is substantial day of week variation in these requests on a regular basis.

Top/Bottom 5 countries by cumulative change

We try to replicate the same graph:

apple_mob %>%
  mutate(date = ymd(date)) %>%
  filter(geo_type == "country/region") %>%
  group_by(region) %>%
  mutate(routing_requests = routing_requests - 100,
         sum_routing_requests = sum(abs(routing_requests))) %>%
  group_by(transportation_type) %>% 
  mutate(change_rank_top = dense_rank(sum_routing_requests)) %>%
  mutate(change_rank_bot = dense_rank(-sum_routing_requests)) %>%
  group_by(region) %>%
  filter(any(change_rank_top %in% 1:5 | change_rank_bot %in% 1:5)) %>%
  ggplot(aes(date, routing_requests, color = region)) +
  geom_line() + facet_wrap(~ transportation_type, scales = "free", ncol = 1) -> p

plotly::ggplotly(p)

We see stronger compliance in transit and walking modes with less variation in driving. This appears consistent with a social distancing order keeping people away from being in physical contact with others.

Location aggregates

apple_mob %>%
  group_by(geo_type) %>%
  summarise_at(vars(region), n_distinct)

Apple mobility data consists of 63 countries and 89 cities around the world. Much more restricted than what’s available in the Google mobility data.

Descartes

Descartes Labs defines their measure of mobilty as the median of max-distance mobility. The max-distance mobility is the maximum haversine distance (km) from the initial location report of the day. Furthermore, a normalized measure of this metric is computed by dividing it by a historical (not clearly defined) value in the region. More information can be found in their technical paper. Time series data begins from March 1st.

Top/Bottom 5 states by cumulative change

descartes_mob %>%
  filter(is.na(admin2)) %>%
  group_by(admin1) %>%
  mutate(m50_index = m50_index - 100,
         sum_m50_index = sum(abs(m50_index))) %>%
  ungroup() %>%
  mutate(change_rank_top = dense_rank(sum_m50_index)) %>%
  mutate(change_rank_bot = dense_rank(-sum_m50_index)) %>%
  group_by(admin1) %>%
  filter(any(change_rank_top %in% 1:5 | change_rank_bot %in% 1:5)) %>%
  ggplot(aes(date, m50_index, color = admin1)) +
  geom_line() -> p

plotly::ggplotly(p)

There’s quite a bit of red-state/blue-state disparity in these changes from baseline.

Location aggregates

descartes_mob %>%
  select(country_code, admin1, admin2) %>% unique()

It looks like we only have US data in the Descartes mobility data - along with state and county breakdowns. DAta is rolled up at a state as well as a county level.

Merging sources

Merging and tidying up into: (a) date, (b) geo_type, (c) geo_name, (d) metric_type, (e) value, and (f) source

all_mob <- bind_rows(
    # Apple Mobility
    apple_mob %>%
      mutate(source = "apple", metric = "routing_requests",
             date = ymd(date)) %>%
      unite(metric, transportation_type, metric) %>%
      rename(value = routing_requests, geo_name = region) %>%
      mutate(geo_name = case_when(
        geo_name == "Baltimore" ~ "Baltimore, Maryland",
        geo_name == "Czech Republic" ~ "Czechia",
        geo_name == "Republic of Korea" ~ "South Korea",
        geo_name == "UK" ~ "United Kingdom",
        geo_name == "Washington DC" ~ "District of Columbia",
        TRUE ~ geo_name
      )) %>%
      mutate(value = value - 100),
    # Google Mobility
    google_mob %>%
      mutate(source = "google") %>%
      mutate(sub_region_2 = if_else(!is.na(sub_region_2),
                                    paste(sub_region_2, sub_region_1,
                                          sep = ", "),
                                    sub_region_2)) %>%
      mutate(
        country_region = if_else(
          !is.na(sub_region_1) | !is.na(sub_region_2), NA_character_,
          country_region),
        sub_region_1 = if_else(
          !is.na(sub_region_2), NA_character_, sub_region_1)) %>%
      pivot_longer(c(country_region, sub_region_1, sub_region_2),
                   names_to = "geo_type",
                   values_to = "geo_name", values_drop_na = TRUE) %>%
      rename(value = pct_change, metric = location_type) %>%
      select(-country_region_code) %>%
      filter(!is.na(geo_name)),
    # Descartes Mobility
    descartes_mob %>%
      mutate(source = "descartes") %>%
      mutate(m50_index = m50_index - 100) %>%
      mutate(admin2 = if_else(!is.na(admin2), paste(admin2, admin1, sep = ", "),
                              admin2)) %>%
      pivot_longer(c(admin1, admin2),
                   names_to = "geo_type",
                   values_to = "geo_name", values_drop_na = TRUE) %>%
      pivot_longer(c(m50, m50_index),
                   names_to = "metric", values_to = "value") %>%
      mutate(geo_name = str_replace(geo_name, "^City of ", "")) %>%
      mutate(geo_name = str_replace(geo_name, "^Sainte ", "Ste. ")) %>%
      mutate(geo_name = str_replace(geo_name, "^Saint ", "St. ")) %>%
      mutate(geo_name = str_replace(geo_name, " Municipality$", "")) %>%
      mutate(geo_name = str_replace(geo_name, " City and Borough$", "")) %>%
      mutate(geo_name = str_replace(geo_name, " Borough$", "")) %>%
      mutate(geo_name = case_when(
        geo_name == "Bronx" ~ "Bronx County",
        geo_name == "Kenai Peninsula" ~ "Kenai Peninsula Borough",
        geo_name == "Dona Ana County" ~ "Doña Ana County",
        geo_name == "Washington, D.C." ~ "District of Columbia",
        TRUE ~ geo_name)) %>%
      select(-country_code, -admin_level, -fips, -samples)) %>%
  # normalize geo_type's
  mutate(geo_type = case_when(
    geo_type %in% c("country/region", "country_region") ~ "country",
    geo_type %in% c("sub_region_1", "admin1") ~ "state_province",
    geo_type %in% c("sub_region_2", "admin2", "city") ~ "county_city",
    TRUE ~ "new_geo_type")) %>%
  mutate(geo_type = if_else(geo_name == "District of Columbia", "county_city",
                            geo_type))

Comparison and validation

Geo overlap

all_mob %>%
  group_by(geo_type, geo_name) %>%
  summarise(n_sources = n_distinct(source),
             sources = str_flatten(unique(source), collapse = ",")) %>%
  arrange(desc(n_sources), geo_type, geo_name) %>% DT::datatable()

There are only two locations where all three data sources align in terms of locations.

Time overlap

all_mob %>%
  select(source, date) %>%
  ggplot(aes(y = date, fill = source)) +
  geom_boxplot() + coord_flip()

Apple covers the largest range of time and starts the earliest. Descartes starts the latest but also seems to have the most updated data. Google is in the middle of the pack.

Google/Apple/Descartes

These sources overlap in only two places, Baltimore and Washington DC.

all_mob %>%
  group_by(geo_type, geo_name) %>%
  mutate(n_sources = n_distinct(source),
         sources = str_flatten(unique(source), collapse = ","),
         week = floor_date(date, "week")) %>%
  filter(n_sources == 3 & !metric %in% c("m50")) %>%
  group_by(week, geo_name, metric) %>%
  summarise(value = mean(value)) %>%
  mutate(metric = str_remove(metric, "_pct_change_from_baseline")) %>%
  ggplot(aes(week, value, color = metric)) +
  theme(legend.position = "bottom") +
  facet_wrap(~ geo_name, ncol = 1) +
  geom_line() -> p
plotly::ggplotly(p)

Google/Apple

Apple/Google overlap happens at the country level. Furthermore, we should expect that Google’s transit station visits aligns well with Apple’s transit direction requests.

all_mob %>%
  group_by(geo_type, geo_name) %>%
  mutate(n_sources = n_distinct(source),
         sources = str_flatten(unique(source), collapse = ","),
         week = floor_date(date, "week")) %>%
  filter(n_sources == 2 & sources == "apple,google") %>%
  unite(metric, source, metric) %>%
  group_by(week, geo_name, metric) %>%
  summarise(value = mean(value)) %>%
  ggplot(aes(week, value, color = geo_name)) +
  facet_wrap(~ metric, ncol = 2) +
  geom_line() -> p
plotly::ggplotly(p)

Transit correlations

all_mob %>%
  filter(metric %in% c("transit_routing_requests",
                       "transit_stations")) %>%
  group_by(geo_name, date) %>%
  filter(any(source == "apple") & any(source == "google")) %>%
  arrange(geo_type, geo_name, date) %>%
  ggplot(aes(date, value, color = source)) +
  geom_line() + facet_wrap(~ geo_name, ncol = 3) +
  theme(legend.position = "bottom")

all_mob %>%
  filter(metric %in% c("transit_routing_requests",
                       "transit_stations")) %>%
  group_by(geo_name, date) %>%
  filter(any(source == "apple") & any(source == "google")) %>%
  ungroup() %>%
  select(-geo_type, -metric) %>%
  spread(source, value) %>%
  group_by(geo_name) %>%
  mutate_at(vars(apple, google), scale) %>%
  summarise(correlation = cor(apple, google)) %>%
  mutate(geo_name = if_else(geo_name %in% c("Taiwan", "Japan"),
                            "Taiwan/Japan", "All other countries")) %>%
  arrange(correlation) %>%
  ggplot(aes(correlation, fill = geo_name)) + geom_density(alpha = 0.2)

Looks like correlations do better in Western countries vs Eastern countries but generally high correlations everywhere except for Taiwan and Japan.

Google/Descartes

There are a lot of counties so we randomly sample 10 for display.

all_mob %>%
  group_by(geo_type, geo_name) %>%
  filter(n_distinct(source) == 2 &
           all(source == "google" | source == "descartes")) %>%
  unite(metric, source, metric) %>%
  group_by(week = floor_date(date, "week"), geo_name, metric) %>%
  summarise(value = mean(value)) %>%
  group_by(geo_name) %>% nest() %>% ungroup() %>%
  sample_n(10) %>% unnest(cols = c(data)) %>%
  ggplot(aes(week, value, color = metric)) +
  facet_wrap(~ geo_name, ncol = 2) +
  geom_line() -> p
plotly::ggplotly(p)

We note that m50 is actually a positive value in kilometers so doesn’t have the same “percent change from baseline” that is used in other metrics.

Residential correlations

Similar to the transit information between Google/Apple - we look for some correlations between Descartes/Google residential vs non-residential movement.

all_mob %>%
  filter(source %in% c("google", "descartes")) %>%
  filter(geo_type == "county_city" & metric != "m50") %>%
  mutate(metric = if_else(metric %in% c("residential", "m50_index", "m50"),
                          metric, "m50_index_goog")) %>%
  group_by(geo_type, geo_name, metric, date, source) %>%
  summarise(value = mean(value, na.rm = TRUE)) %>%
  #mutate(value = if_else(metric == "residential", value * -1, value)) %>%
  group_by(metric, date) %>%
  summarise_at(vars(value), mean, na.rm=TRUE) %>%
  ggplot(aes(date, value, color = metric)) + geom_line()

all_mob %>%
  filter(source %in% c("google", "descartes")) %>%
  filter(geo_type == "county_city" & metric != "m50") %>%
  mutate(metric = if_else(metric %in% c("residential", "m50_index", "m50"),
                          metric, "m50_index_goog")) %>%
  group_by(geo_type, geo_name, metric, date, source) %>%
  summarise(value = mean(value, na.rm = TRUE)) %>% ungroup() %>%
  select(geo_name, metric, date, value) %>%
  spread(metric, value) %>%
  group_by(geo_name) %>%
  filter(any(!is.na(m50_index) & any(!is.na(m50_index_goog)))) %>%
  group_by(geo_name) %>%
  summarise(correlation_m50 = cor(m50_index, m50_index_goog,
                                  use = "pairwise.complete.obs"),
            correlation_res = cor(m50_index, residential,
                                  use = "pairwise.complete.obs")) %>%
  gather(correlation_type, correlation, starts_with("correlation_")) %>%
  arrange(-correlation) %>%
  ggplot(aes(correlation, fill = correlation_type)) +
  geom_density(alpha = 0.1)
the standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zero

This is all reassuring, Google residential activity is negatively correlated with the m50_index from Descartes. The average of all non-residential activities from Google is positively correlated to the m50_index. The residential negative correlation does appear stronger over these different regions than non-residential Google mobility data.

Aggregating and cleaning data

Below we aggregate data into measures that reflect declining mobility. This means aggregating all the Apple metrics directly, aggregating all the Google metrics treating residential activity inversely, and using the normalized Descartes measure directly. We also interpolate data missing in the Google data due to privacy thresholds.

Missingness

all_mob %>%
  group_by(geo_type, metric, source) %>%
  summarise(na_pct = mean(is.na(value))) %>%
  arrange(desc(na_pct))

Google is missing a fair bit of data in its stats about parks, transit_stations, and residential - especially at the county level. It’s a little strange that there is missing data at the state level and the order of missingness is reversed from the county level. There is this general issue where data is censored when there are very few people in a location or location category - so maybe the missingness is information in itself - like a lower bound on the amount of activity in a given place. This missingness is likely to cause some problems so we should come up with a way to deal with it.

We create all_mob_fix which adds another column, value_type that helps us distinguish between values interpolated by na.approx and original values. There are many spotty values, especially at the county level, in the Google data so this is necessary to do anything useful.

Filling and aggregating

(all_mob_fix <- all_mob %>%
   # fill in missing values
   group_by(metric, geo_type, geo_name, source) %>%
   mutate(value_approx = na.approx(value, na.rm = FALSE)) %>%
   gather(value_type, value, value, value_approx) %>%
   ungroup() %>% 
   # aggregate google mobility metrics
   mutate(value = if_else(metric == "residential", value * -1, value)) %>%
   mutate(metric = if_else(source == "google", "google_mob", metric)) %>%
   mutate(metric = if_else(source == "apple", "apple_mob", metric)) %>%
   filter(metric != "m50") %>%
   mutate(metric = if_else(source == "descartes", "descartes_mob", metric)) %>%
   group_by(metric, geo_type, geo_name, source, value_type, date) %>%
   summarise(value = mean(value, na.rm = TRUE)) %>%
   ungroup()) #%>%
  # group_by(metric, geo_type, geo_name, source) %>% pull(pct_value) %>% hist()
  # filter(any(between(pct_value, 0.96, 0.97))) %>% 
  # filter(rbinom(1, 1, 0.1) == 1) %>%
  # ggplot(aes(date, value)) +
  # facet_grid(geo_name ~ value_type) +
  # geom_line()

Mobility parameters

We’d like to be able to extract key parameters from the aggregated mobility time series. Specifically we’re interested in (1) when the declines in mobility began, (2) how quickly and by how much did mobility decline, and (3) is there any evidence of a reopening.

Change point detection

This algorithm looks for significant changes in time series over time and helps us find the points in time when these changes occurred.

help_envcpt <- function(df) {
  if (sum(!is.na(df$value)) < 14) {
    return(NA) 
  } else {
    return(list(envcpt(na.approx(df$value), verbose = FALSE)))
  }
}

mob_cpt_file <- "data/all_mob_cpt.rds"

if (!file.exists(mob_cpt_file)) {
 all_mob_cpt <- all_mob_fix %>%
   filter(value_type == "value_approx") %>%
   group_by(geo_type, geo_name, source, metric) %>%
   nest() %>% rowwise() %>%
   mutate(cpt_mdl = help_envcpt(data)) 
 saveRDS(all_mob_cpt, mob_cpt_file)
} else {
  all_mob_cpt <- readRDS(mob_cpt_file)
}
all_mob_cpt %>% rowwise() %>%
  mutate(has_mdl = all(!is.na(cpt_mdl))) %>%
  group_by(geo_type, metric) %>%
  summarise(has_mdl = mean(has_mdl)) %>%
  arrange(has_mdl)
Grouping rowwise data frame strips rowwise nature

Most models were able to run with the exception of a few, probably due to sparse data.

Extract mobility parameters

extract_mob_parameters <- function(cpt_data, cpt_mdl, mdl_name = "meancpt") {
  if (any(is.na(cpt_mdl))) {
    return(NA)
  }
  cpt_mdl <- cpt_mdl[[mdl_name]]
  dates <- cpt_data %>% pull(date)
  values <- cpt_data %>% pull(value)
  
  means <- cpt_mdl@param.est$mean
  vars <- cpt_mdl@param.est$variance
  
  end_pts <- cpt_mdl@cpts
  seg_lens <- seg.len(cpt_mdl)
  start_pts <- (end_pts - seg_lens) + 1
  
  eq_idx <- head(order(seg_lens, decreasing = T), 2)
  min_idx <- which.min(means)
  max_idx <- which.max(means)
  first_idx <- min(min_idx, max_idx)
  last_idx <- max(min_idx, max_idx)
  
  # important vals
  change_start <- end_pts[first_idx]
  change_end <- start_pts[last_idx]
  
  # save vals
  change_start_date <- dates[change_start]
  change_end_date <- dates[change_end]
  
  mean_before <- means[first_idx]
  mean_after <- means[last_idx]
  var_before <- vars[first_idx]
  var_after <- vars[last_idx]
  change_diff <- mean_after - mean_before
  change_slope <- change_diff / (last_idx - first_idx)
  start_seg <- c(start_pts[first_idx], end_pts[first_idx])
  start_seg_dates <- dates[start_seg]
  end_seg <- c(start_pts[last_idx], end_pts[last_idx])
  end_seg_dates <- dates[end_seg]
  revert <- last(means) - mean_after
  
  data.frame(
    change_start_date = change_start_date,
    change_diff = change_diff,
    change_slope = change_slope,
    change_end_date = change_end_date,
    change_days = change_end_date - change_start_date,
    mean_before = mean_before,
    mean_after= mean_after,
    var_before = var_before,
    var_after= var_after,
    seg_start_date = dates[start_pts[first_idx]],
    seg_end_date = dates[end_pts[last_idx]],
    revert = revert)
}

all_mob_params <- all_mob_cpt %>%
  rowwise() %>%
  mutate(
    cpt_params = list(extract_mob_parameters(data, cpt_mdl))) %>%
  unnest(cpt_params) %>% ungroup()
plot_row <- function(x) {
  x %>% pluck("cpt_mdl", 1) %>% .[["meancpt"]] %>% plot()
}

all_mob_params %>%
  arrange(change_slope) %>%
  select(source, geo_name, change_slope) %>%
  head(100) %>%
  DT::datatable()

The first thing that pops out are Descartes numbers are wild (and likely very noisy). We should probably ignore them.

all_mob_params %>%
  filter(source != "descartes") %>%
  arrange(change_slope) %>%
  select(source, geo_name, change_slope) %>%
  head(100) %>%
  DT::datatable()

Apple and Google change from baseline metrics look reasonable. We’ll use Google for a region level analysis and Apple+Google for country level changes.

all_mob_params %>%
  filter(geo_type == "country") %>%
  select(source, geo_name, change_start_date) %>%
  spread(source, change_start_date) %>%
  ggplot(aes(google, apple)) + geom_point()

Most of the change dates in march are in agreement - but some earlier change dates picked up by Apple aren’t picked up by Google because the data set started later than Apple.

all_mob_params %>%
  filter(geo_type == "country") %>%
  select(source, geo_name, change_start_date) %>%
  spread(source, change_start_date) %>%
  mutate(diff = abs(google - apple)) %>%
  arrange(desc(diff))

Here we see that many Asia region countries and countries that started their shutdown earlier were picked up in the Apple data earlier. For countries that started their shutdown later in March there wasn’t as much of a difference - which is promising for our change point detecton model.

all_mob_params %>%
  filter(geo_name == "Hong Kong") %>% slice(1) %>% plot_row()


all_mob_params %>%
  filter(geo_name == "Hong Kong") %>% slice(2) %>% plot_row()

Google looks like they are not getting the correct shutdown dates for early implementesr because the time series only begins in March while Apple’s start in April. At least for the late implementers, the numbers seem to align reasonably well. This tells us that using Google data for an international comparison may not be useful because countries didn’t start until earlier. For countries like Italy, who started later (sadly), we are able to see the change points and it looks like we’re able to pick up the start date correctly.

all_mob_params %>%
  filter(geo_name == "Italy") %>% slice(1) %>% plot_row()


all_mob_params %>%
  filter(geo_name == "Italy") %>% slice(2) %>% plot_row()

Appendix

LS0tCnRpdGxlOiAiTW9iaWxpdHkgZGF0YSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBub3RlYm9vayBleHBsb3JlcywgdmFsaWRhdGVzLCBhbmQgZXh0cmFjdHMga2V5IHBhcmFtZXRlcnMgZnJvbSBwdWJsaWMgbW9iaWxpdHkgYXZhaWxhYmxlIGZyb20gR29vZ2xlLCBBcHBsZSwgYW5kIERlc2NhcnRlcyBMYWJzLgoKIyBTZXQgdXAgYW5kIHB1bGxpbmcgZGF0YQoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShFbnZDcHQpICMgZm9yIGNoYW5nZSBwb2ludCBkZXRlY3Rpb24KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobHVicmlkYXRlKQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cmdvb2dsZV9tb2IgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vd3d3LmdzdGF0aWMuY29tL2NvdmlkMTkvbW9iaWxpdHkvR2xvYmFsX01vYmlsaXR5X1JlcG9ydC5jc3YiLAogICMgY29sX3R5cGVzIGIvYyByZWFkX2NzdiBkZXRlY3RzIGxvZ2ljYWwgaW4gbWlzc2luZyB2YWx1ZXMgaW4gZmlyc3QgMTAwMCByb3dzLgogIGNvbF90eXBlcyA9ICJjY2NjRG5ubm5ubiIsICkKYXBwbGVfbW9iIDwtIHJlYWRfY3N2KAogICJodHRwczovL2NvdmlkMTktc3RhdGljLmNkbi1hcHBsZS5jb20vY292aWQxOS1tb2JpbGl0eS1kYXRhLzIwMDZIb3RmaXhEZXYxMS92MS9lbi11cy9hcHBsZW1vYmlsaXR5dHJlbmRzLTIwMjAtMDQtMjAuY3N2IikKZGVzY2FydGVzX21vYiA8LSByZWFkX2NzdigKICAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Rlc2NhcnRlc2xhYnMvREwtQ09WSUQtMTkvbWFzdGVyL0RMLXVzLW1vYmlsaXR5LWRhdGVyb3cuY3N2IikKYGBgCgojIERhdGEgRXhwbG9yYXRpb24KCiMjIERhdGEgc3VtbWFyaWVzCgpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpsYXBwbHkobGlzdChnb29nbGVfbW9iLCBhcHBsZV9tb2IsIGRlc2NhcnRlc19tb2IpLCBGVU4gPSBmdW5jdGlvbihkZikgewogIGRmICU+JSBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBhcy5mYWN0b3IpICU+JSBzdW1tYXJ5KCkKfSkKYGBgCgpUaGVyZSBpcyBzb21lIHN1YnN0YW50aWFsIGRpZmZlcmVuY2VzIGluIHRoZXNlIGRhdGEgc2V0cyBpbiB0aGVpciBzdHJ1Y3R1cmUsIGJ1dCBhbHNvIG9uIHRoZWlyIGNvbnRlbnQuIFdlIGZpcnN0IHRpZHkgdXAgdGhlIHN0cnVjdHVyZSwgdGhlbiB3ZSBkaXZlIGludG8gZWFjaCBzZXQgb2YgbWV0cmljcy4KCiMjIFRpZHkgc3RydWN0dXJlCgpHb29nbGU6CgpgYGB7cn0KKGdvb2dsZV9tb2IgPC0gZ29vZ2xlX21vYiAlPiUKICBnYXRoZXIobG9jYXRpb25fdHlwZSwgcGN0X2NoYW5nZSwgY29udGFpbnMoInBlcmNlbnRfY2hhbmdlIikpICU+JQogIG11dGF0ZShsb2NhdGlvbl90eXBlID0gc3RyX3JlbW92ZShsb2NhdGlvbl90eXBlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiX3BlcmNlbnRfY2hhbmdlX2Zyb21fYmFzZWxpbmUiKSkpCmBgYAoKQXBwbGU6CgpgYGB7cn0KKGFwcGxlX21vYiA8LSBhcHBsZV9tb2IgJT4lCiAgIGdhdGhlcihkYXRlLCByb3V0aW5nX3JlcXVlc3RzLCAtZ2VvX3R5cGUsIC1yZWdpb24sIC10cmFuc3BvcnRhdGlvbl90eXBlKSkKYGBgCgpEZXNjYXJ0ZXMgaXMgYWxyZWFkeSB0aWR5LgoKIyMgR29vZ2xlCgpbR29vZ2xlIG1vYmlsaXR5XShodHRwczovL3d3dy5nb29nbGUuY29tL2NvdmlkMTkvbW9iaWxpdHkvZGF0YV9kb2N1bWVudGF0aW9uLmh0bWwpIG1lYXN1cmVzICJ0aGUgcGVyY2VudCBjaGFuZ2UgaW4gdmlzaXRzIHRvIHBsYWNlcyBsaWtlIGdyb2Nlcnkgc3RvcmVzIGFuZCBwYXJrcyB3aXRoaW4gYSBnZW9ncmFwaGljIGFyZWEiLiBUaGlzIGlzIHNwbGl0IG91dCBpbnRvIHNpeCBsb2NhdGlvbiB0eXBlcyAocmV0YWlsL3JlY3JlYXRpb24sIGdyb2NlcnkvcGhhcm1hY3ksIHBhcmtzLCB0cmFuc2l0IHN0YXRpb25zLCB3b3JrcGxhY2VzLCBhbmQgcmVzaWRlbnRpYWwpIGFuZCBtZWFzdXJlIGNoYW5nZXMgZnJvbSBhIGJhc2VsaW5lIGRhdGUgb2YgRmViIDE1LCAyMDIwLgoKSXQgd291bGQgYmUgaW50ZXJlc3Rpbmcga25vdyBob3cgdmlzaXRzIGFyZSBjb3VudGVkLiBEbyBJIG5lZWQgdG8gbGVhdmUgYSBwbGFjZSB0byB2aXNpdCB0aGF0IHBsYWNlIGFnYWluPyBEb2VzIHN0YXlpbmcgYXQgaG9tZSBhbGwgZGF5IG1lYW4gSSB2aXNpdGVkIGhvbWUganVzdCBvbmNlPyBUaGVzZSBkZXRhaWxzIGFyZSBub3QgcmV2ZWFsZWQgZnJvbSB0aGUgW3N1bW1hcnldKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vY292aWQxOS9tb2JpbGl0eS9kYXRhX2RvY3VtZW50YXRpb24uaHRtbCkuCgojIyMgVG9wL0JvdHRvbSA1IGNvdW50cmllcyBieSBjdW11bGF0aXZlIGNoYW5nZQoKVGhlIG1vYmlsaXR5IGRvY3VtZW50YXRpb24gYWxzbyB3YXJuczogIkxvY2F0aW9uIGFjY3VyYWN5IGFuZCB0aGUgdW5kZXJzdGFuZGluZyBvZiBjYXRlZ29yaXplZCBwbGFjZXMgdmFyaWVzIGZyb20gcmVnaW9uIHRvIHJlZ2lvbiwgc28gd2UgZG9u4oCZdCByZWNvbW1lbmQgdXNpbmcgdGhpcyBkYXRhIHRvIGNvbXBhcmUgY2hhbmdlcyBiZXR3ZWVuIGNvdW50cmllcywgb3IgYmV0d2VlbiByZWdpb25zIHdpdGggZGlmZmVyZW50IGNoYXJhY3RlcmlzdGljcyAoZS5nLiBydXJhbCB2ZXJzdXMgdXJiYW4gYXJlYXMpLiIKCkl0J3MgYSBnb29kIGNhdmVhdCB0byB0aGluayBhYm91dCAtIGJ1dCB3ZSBuZWVkIHRvIHN0YXJ0IHNvbWV3aGVyZToKCmBgYHtyIGZpZy5oZWlnaHQ9OSwgZmlnLndpZHRoPTl9Cmdvb2dsZV9tb2IgJT4lCiAgZmlsdGVyKGlzLm5hKHN1Yl9yZWdpb25fMSkgJiBpcy5uYShzdWJfcmVnaW9uXzIpKSAlPiUKICBncm91cF9ieShjb3VudHJ5X3JlZ2lvbiwgbG9jYXRpb25fdHlwZSkgJT4lCiAgbXV0YXRlKHN1bV9wY3RfY2hhbmdlID0gc3VtKGFicyhwY3RfY2hhbmdlKSkpICU+JQogIGdyb3VwX2J5KGxvY2F0aW9uX3R5cGUpICU+JSAKICBtdXRhdGUoY2hhbmdlX3JhbmtfdG9wID0gZGVuc2VfcmFuayhzdW1fcGN0X2NoYW5nZSkpICU+JQogIG11dGF0ZShjaGFuZ2VfcmFua19ib3QgPSBkZW5zZV9yYW5rKC1zdW1fcGN0X2NoYW5nZSkpICU+JQogIGdyb3VwX2J5KGNvdW50cnlfcmVnaW9uKSAlPiUKICBmaWx0ZXIoYW55KGNoYW5nZV9yYW5rX3RvcCAlaW4lIDE6NSB8IGNoYW5nZV9yYW5rX2JvdCAlaW4lIDE6NSkpICU+JQogIGdncGxvdChhZXMoZGF0ZSwgcGN0X2NoYW5nZSwgY29sb3IgPSBjb3VudHJ5X3JlZ2lvbikpICsKICBmYWNldF93cmFwKH4gbG9jYXRpb25fdHlwZSwgbmNvbCA9IDIpICsKICBnZW9tX2xpbmUoKSAtPiBwCgpwbG90bHk6OmdncGxvdGx5KHApCmBgYAoKSXQgYXBwZWFycyB0aGUgY291bnRyaWVzIHRoYXQgaGF2ZSBjaGFuZ2VkIHRoZSBsZWFzdCBhcmUgQmVuaW4sIEphcGFuLCBUYWl3YW4sIFRhbnphbmlhLCBhbmQgWWVtZW4uICBUaG9zZSB0aGF0IGNoYW5nZWQgdGhlIG1vc3QgYXJlIEJvbGl2aWEsIEVjdWFkb3IsIEl0YWx5LCBNYXVyaXRpdXMsIGFuZCBTcGFpbi4gIEluIGdlbmVyYWwgdGhlcmUgaXMgYSBjb25zaXN0ZW50IGRlY2xpbmUgaW4gYWxsIGNhdGVnb3JpZXMgZXhjZXB0IHBhcmtzIGFuZCByZXNpZGVudGlhbC4gIFRoYXQgd291bGQgbW9yZSBvciBsZXNzIGFsaWduIHdpdGggd2hhdCB3ZSB3b3VsZCBleHBlY3QgZnJvbSBhIHN0YXktYXQtaG9tZSBvcmRlciB3aXRoIHNvbWUgb3V0ZG9vciBleGNlcHRpb25zLiBEZW5tYXJrIHJlYWxseSBsb3ZlcyB0aGVpciBwYXJrcy4KCiMjIyBMb2NhdGlvbiBhZ2dyZWdhdGVzCgpgYGB7cn0KZ29vZ2xlX21vYiAlPiUgZ3JvdXBfYnkoY291bnRyeV9yZWdpb24pICU+JQogIHN1bW1hcmlzZShkaXN0aW5jdF9zcjEgPSBuX2Rpc3RpbmN0KHN1Yl9yZWdpb25fMSwgbmEucm0gPSBUKSwKICAgICAgICAgICAgZGlzdGluY3Rfc3IyID0gbl9kaXN0aW5jdChzdWJfcmVnaW9uXzIsIG5hLnJtID0gVCkpICU+JQogIGFycmFuZ2UoZGVzYyhkaXN0aW5jdF9zcjIpLCBkZXNjKGRpc3RpbmN0X3NyMSkpCmBgYAoKVGhlIEdvb2dsZSBtb2JpbGl0eSBkYXRhIGNvbnRhaW5zIHR3byBzdWItcmVnaW9ucyBmb3IgdGhlIFVTIChzdGF0ZXMgYW5kIGNvdW50aWVzKSBhbmQgb25lIHN1Yi1yZWdpb24gZm9yIDQ5IG90aGVyIGNvdW50cmllcy4gIFRoZSByZW1haW5pbmcgODIgY291bnRyaWVzIGRvIG5vdCBoYXZlIGFueSBzdWItcmVnaW9uYWwgaW5mb3JtYXRpb24uCgojIyBBcHBsZQoKQXBwbGUgW2RlZmluZXNdKGh0dHBzOi8vd3d3LmFwcGxlLmNvbS9jb3ZpZDE5L21vYmlsaXR5KSB0aGVpciBtZWFzdXJlIGFzICJhIHJlbGF0aXZlIHZvbHVtZSBvZiBkaXJlY3Rpb25zIHJlcXVlc3RzIHBlciBjb3VudHJ5L3JlZ2lvbiBvciBjaXR5IGNvbXBhcmVkIHRvIGEgYmFzZWxpbmUgdm9sdW1lIG9uIEphbnVhcnkgMTN0aCwgMjAyMCIuIFNvIHRoaXMgc3RhcnRzIGVhcmxpZXIgdGhhbiB0aGUgR29vZ2xlIGRhdGEgYW5kIGN1dHMgdGhlaXIgZGF0YSBieSB0cmFuc3BvcnRhdGlvbiB0eXBlIChkcml2aW5nLCB3YWxraW5nLCBvciB0cmFuc2l0KS4gIFBlcmhhcHMgdGhlIHRyYW5zaXQgZGlyZWN0aW9uIHJlcXVlc3RzIGNvdWxkIGJlIGNvbXBhcmFibGUgdG8gR29vZ2xlJ3MuCgpBZGRpdGlvbmFsbHkgdGhleSBkZWZpbmUgdGhlaXIgZGF5cyBhcyBtaWRuaWdodCB0byBtaWRuaWdodCwgUGFjaWZpYyB0aW1lIGFuZCBwb2ludCBvdXQgdGhhdCB0aGVyZSBpcyBzdWJzdGFudGlhbCBkYXkgb2Ygd2VlayB2YXJpYXRpb24gaW4gdGhlc2UgcmVxdWVzdHMgb24gYSByZWd1bGFyIGJhc2lzLgoKIyMjIFRvcC9Cb3R0b20gNSBjb3VudHJpZXMgYnkgY3VtdWxhdGl2ZSBjaGFuZ2UKCldlIHRyeSB0byByZXBsaWNhdGUgdGhlIHNhbWUgZ3JhcGg6CgpgYGB7ciBmaWcuaGVpZ2h0PTksIGZpZy53aWR0aCA9IDl9CmFwcGxlX21vYiAlPiUKICBtdXRhdGUoZGF0ZSA9IHltZChkYXRlKSkgJT4lCiAgZmlsdGVyKGdlb190eXBlID09ICJjb3VudHJ5L3JlZ2lvbiIpICU+JQogIGdyb3VwX2J5KHJlZ2lvbikgJT4lCiAgbXV0YXRlKHJvdXRpbmdfcmVxdWVzdHMgPSByb3V0aW5nX3JlcXVlc3RzIC0gMTAwLAogICAgICAgICBzdW1fcm91dGluZ19yZXF1ZXN0cyA9IHN1bShhYnMocm91dGluZ19yZXF1ZXN0cykpKSAlPiUKICBncm91cF9ieSh0cmFuc3BvcnRhdGlvbl90eXBlKSAlPiUgCiAgbXV0YXRlKGNoYW5nZV9yYW5rX3RvcCA9IGRlbnNlX3Jhbmsoc3VtX3JvdXRpbmdfcmVxdWVzdHMpKSAlPiUKICBtdXRhdGUoY2hhbmdlX3JhbmtfYm90ID0gZGVuc2VfcmFuaygtc3VtX3JvdXRpbmdfcmVxdWVzdHMpKSAlPiUKICBncm91cF9ieShyZWdpb24pICU+JQogIGZpbHRlcihhbnkoY2hhbmdlX3JhbmtfdG9wICVpbiUgMTo1IHwgY2hhbmdlX3JhbmtfYm90ICVpbiUgMTo1KSkgJT4lCiAgZ2dwbG90KGFlcyhkYXRlLCByb3V0aW5nX3JlcXVlc3RzLCBjb2xvciA9IHJlZ2lvbikpICsKICBnZW9tX2xpbmUoKSArIGZhY2V0X3dyYXAofiB0cmFuc3BvcnRhdGlvbl90eXBlLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSAxKSAtPiBwCgpwbG90bHk6OmdncGxvdGx5KHApCmBgYAoKV2Ugc2VlIHN0cm9uZ2VyIGNvbXBsaWFuY2UgaW4gdHJhbnNpdCBhbmQgd2Fsa2luZyBtb2RlcyB3aXRoIGxlc3MgdmFyaWF0aW9uIGluIGRyaXZpbmcuIFRoaXMgYXBwZWFycyBjb25zaXN0ZW50IHdpdGggYSBzb2NpYWwgZGlzdGFuY2luZyBvcmRlciBrZWVwaW5nIHBlb3BsZSBhd2F5IGZyb20gYmVpbmcgaW4gcGh5c2ljYWwgY29udGFjdCB3aXRoIG90aGVycy4KCiMjIyBMb2NhdGlvbiBhZ2dyZWdhdGVzCgpgYGB7cn0KYXBwbGVfbW9iICU+JQogIGdyb3VwX2J5KGdlb190eXBlKSAlPiUKICBzdW1tYXJpc2VfYXQodmFycyhyZWdpb24pLCBuX2Rpc3RpbmN0KQpgYGAKCkFwcGxlIG1vYmlsaXR5IGRhdGEgY29uc2lzdHMgb2YgNjMgY291bnRyaWVzIGFuZCA4OSBjaXRpZXMgYXJvdW5kIHRoZSB3b3JsZC4gTXVjaCBtb3JlIHJlc3RyaWN0ZWQgdGhhbiB3aGF0J3MgYXZhaWxhYmxlIGluIHRoZSBHb29nbGUgbW9iaWxpdHkgZGF0YS4KCiMjIERlc2NhcnRlcwoKRGVzY2FydGVzIExhYnMgZGVmaW5lcyB0aGVpciBtZWFzdXJlIG9mIG1vYmlsdHkgYXMgdGhlIG1lZGlhbiBvZiBtYXgtZGlzdGFuY2UgbW9iaWxpdHkuIFRoZSBtYXgtZGlzdGFuY2UgbW9iaWxpdHkgaXMgdGhlIG1heGltdW0gaGF2ZXJzaW5lIGRpc3RhbmNlIChrbSkgZnJvbSB0aGUgaW5pdGlhbCBsb2NhdGlvbiByZXBvcnQgb2YgdGhlIGRheS4gIEZ1cnRoZXJtb3JlLCBhIG5vcm1hbGl6ZWQgbWVhc3VyZSBvZiB0aGlzIG1ldHJpYyBpcyBjb21wdXRlZCBieSBkaXZpZGluZyBpdCBieSBhIGhpc3RvcmljYWwgKG5vdCBjbGVhcmx5IGRlZmluZWQpIHZhbHVlIGluIHRoZSByZWdpb24uIE1vcmUgaW5mb3JtYXRpb24gY2FuIGJlIGZvdW5kIGluIHRoZWlyIFt0ZWNobmljYWwgcGFwZXJdKGh0dHBzOi8vd3d3LmRlc2NhcnRlc2xhYnMuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDIwLzAzL21vYmlsaXR5LXYwOTcucGRmKS4gVGltZSBzZXJpZXMgZGF0YSBiZWdpbnMgZnJvbSBNYXJjaCAxc3QuCgojIyMgVG9wL0JvdHRvbSA1IHN0YXRlcyBieSBjdW11bGF0aXZlIGNoYW5nZQoKYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9OH0KZGVzY2FydGVzX21vYiAlPiUKICBmaWx0ZXIoaXMubmEoYWRtaW4yKSkgJT4lCiAgZ3JvdXBfYnkoYWRtaW4xKSAlPiUKICBtdXRhdGUobTUwX2luZGV4ID0gbTUwX2luZGV4IC0gMTAwLAogICAgICAgICBzdW1fbTUwX2luZGV4ID0gc3VtKGFicyhtNTBfaW5kZXgpKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShjaGFuZ2VfcmFua190b3AgPSBkZW5zZV9yYW5rKHN1bV9tNTBfaW5kZXgpKSAlPiUKICBtdXRhdGUoY2hhbmdlX3JhbmtfYm90ID0gZGVuc2VfcmFuaygtc3VtX201MF9pbmRleCkpICU+JQogIGdyb3VwX2J5KGFkbWluMSkgJT4lCiAgZmlsdGVyKGFueShjaGFuZ2VfcmFua190b3AgJWluJSAxOjUgfCBjaGFuZ2VfcmFua19ib3QgJWluJSAxOjUpKSAlPiUKICBnZ3Bsb3QoYWVzKGRhdGUsIG01MF9pbmRleCwgY29sb3IgPSBhZG1pbjEpKSArCiAgZ2VvbV9saW5lKCkgLT4gcAoKcGxvdGx5OjpnZ3Bsb3RseShwKQpgYGAKClRoZXJlJ3MgcXVpdGUgYSBiaXQgb2YgcmVkLXN0YXRlL2JsdWUtc3RhdGUgZGlzcGFyaXR5IGluIHRoZXNlIGNoYW5nZXMgZnJvbSBiYXNlbGluZS4KCiMjIyBMb2NhdGlvbiBhZ2dyZWdhdGVzCgpgYGB7cn0KZGVzY2FydGVzX21vYiAlPiUKICBzZWxlY3QoY291bnRyeV9jb2RlLCBhZG1pbjEsIGFkbWluMikgJT4lIHVuaXF1ZSgpCmBgYAoKSXQgbG9va3MgbGlrZSB3ZSBvbmx5IGhhdmUgVVMgZGF0YSBpbiB0aGUgRGVzY2FydGVzIG1vYmlsaXR5IGRhdGEgLSBhbG9uZyB3aXRoIHN0YXRlIGFuZCBjb3VudHkgYnJlYWtkb3ducy4gREF0YSBpcyByb2xsZWQgdXAgYXQgYSBzdGF0ZSBhcyB3ZWxsIGFzIGEgY291bnR5IGxldmVsLgoKIyBNZXJnaW5nIHNvdXJjZXMKCk1lcmdpbmcgYW5kIHRpZHlpbmcgdXAgaW50bzogKGEpIGRhdGUsIChiKSBnZW9fdHlwZSwgKGMpIGdlb19uYW1lLCAoZCkgbWV0cmljX3R5cGUsIChlKSB2YWx1ZSwgYW5kIChmKSBzb3VyY2UKCmBgYHtyfQphbGxfbW9iIDwtIGJpbmRfcm93cygKICAgICMgQXBwbGUgTW9iaWxpdHkKICAgIGFwcGxlX21vYiAlPiUKICAgICAgbXV0YXRlKHNvdXJjZSA9ICJhcHBsZSIsIG1ldHJpYyA9ICJyb3V0aW5nX3JlcXVlc3RzIiwKICAgICAgICAgICAgIGRhdGUgPSB5bWQoZGF0ZSkpICU+JQogICAgICB1bml0ZShtZXRyaWMsIHRyYW5zcG9ydGF0aW9uX3R5cGUsIG1ldHJpYykgJT4lCiAgICAgIHJlbmFtZSh2YWx1ZSA9IHJvdXRpbmdfcmVxdWVzdHMsIGdlb19uYW1lID0gcmVnaW9uKSAlPiUKICAgICAgbXV0YXRlKGdlb19uYW1lID0gY2FzZV93aGVuKAogICAgICAgIGdlb19uYW1lID09ICJCYWx0aW1vcmUiIH4gIkJhbHRpbW9yZSwgTWFyeWxhbmQiLAogICAgICAgIGdlb19uYW1lID09ICJDemVjaCBSZXB1YmxpYyIgfiAiQ3plY2hpYSIsCiAgICAgICAgZ2VvX25hbWUgPT0gIlJlcHVibGljIG9mIEtvcmVhIiB+ICJTb3V0aCBLb3JlYSIsCiAgICAgICAgZ2VvX25hbWUgPT0gIlVLIiB+ICJVbml0ZWQgS2luZ2RvbSIsCiAgICAgICAgZ2VvX25hbWUgPT0gIldhc2hpbmd0b24gREMiIH4gIkRpc3RyaWN0IG9mIENvbHVtYmlhIiwKICAgICAgICBUUlVFIH4gZ2VvX25hbWUKICAgICAgKSkgJT4lCiAgICAgIG11dGF0ZSh2YWx1ZSA9IHZhbHVlIC0gMTAwKSwKICAgICMgR29vZ2xlIE1vYmlsaXR5CiAgICBnb29nbGVfbW9iICU+JQogICAgICBtdXRhdGUoc291cmNlID0gImdvb2dsZSIpICU+JQogICAgICBtdXRhdGUoc3ViX3JlZ2lvbl8yID0gaWZfZWxzZSghaXMubmEoc3ViX3JlZ2lvbl8yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoc3ViX3JlZ2lvbl8yLCBzdWJfcmVnaW9uXzEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIsICIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJfcmVnaW9uXzIpKSAlPiUKICAgICAgbXV0YXRlKAogICAgICAgIGNvdW50cnlfcmVnaW9uID0gaWZfZWxzZSgKICAgICAgICAgICFpcy5uYShzdWJfcmVnaW9uXzEpIHwgIWlzLm5hKHN1Yl9yZWdpb25fMiksIE5BX2NoYXJhY3Rlcl8sCiAgICAgICAgICBjb3VudHJ5X3JlZ2lvbiksCiAgICAgICAgc3ViX3JlZ2lvbl8xID0gaWZfZWxzZSgKICAgICAgICAgICFpcy5uYShzdWJfcmVnaW9uXzIpLCBOQV9jaGFyYWN0ZXJfLCBzdWJfcmVnaW9uXzEpKSAlPiUKICAgICAgcGl2b3RfbG9uZ2VyKGMoY291bnRyeV9yZWdpb24sIHN1Yl9yZWdpb25fMSwgc3ViX3JlZ2lvbl8yKSwKICAgICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gImdlb190eXBlIiwKICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJnZW9fbmFtZSIsIHZhbHVlc19kcm9wX25hID0gVFJVRSkgJT4lCiAgICAgIHJlbmFtZSh2YWx1ZSA9IHBjdF9jaGFuZ2UsIG1ldHJpYyA9IGxvY2F0aW9uX3R5cGUpICU+JQogICAgICBzZWxlY3QoLWNvdW50cnlfcmVnaW9uX2NvZGUpICU+JQogICAgICBmaWx0ZXIoIWlzLm5hKGdlb19uYW1lKSksCiAgICAjIERlc2NhcnRlcyBNb2JpbGl0eQogICAgZGVzY2FydGVzX21vYiAlPiUKICAgICAgbXV0YXRlKHNvdXJjZSA9ICJkZXNjYXJ0ZXMiKSAlPiUKICAgICAgbXV0YXRlKG01MF9pbmRleCA9IG01MF9pbmRleCAtIDEwMCkgJT4lCiAgICAgIG11dGF0ZShhZG1pbjIgPSBpZl9lbHNlKCFpcy5uYShhZG1pbjIpLCBwYXN0ZShhZG1pbjIsIGFkbWluMSwgc2VwID0gIiwgIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkbWluMikpICU+JQogICAgICBwaXZvdF9sb25nZXIoYyhhZG1pbjEsIGFkbWluMiksCiAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJnZW9fdHlwZSIsCiAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiZ2VvX25hbWUiLCB2YWx1ZXNfZHJvcF9uYSA9IFRSVUUpICU+JQogICAgICBwaXZvdF9sb25nZXIoYyhtNTAsIG01MF9pbmRleCksCiAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJtZXRyaWMiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUKICAgICAgbXV0YXRlKGdlb19uYW1lID0gc3RyX3JlcGxhY2UoZ2VvX25hbWUsICJeQ2l0eSBvZiAiLCAiIikpICU+JQogICAgICBtdXRhdGUoZ2VvX25hbWUgPSBzdHJfcmVwbGFjZShnZW9fbmFtZSwgIl5TYWludGUgIiwgIlN0ZS4gIikpICU+JQogICAgICBtdXRhdGUoZ2VvX25hbWUgPSBzdHJfcmVwbGFjZShnZW9fbmFtZSwgIl5TYWludCAiLCAiU3QuICIpKSAlPiUKICAgICAgbXV0YXRlKGdlb19uYW1lID0gc3RyX3JlcGxhY2UoZ2VvX25hbWUsICIgTXVuaWNpcGFsaXR5JCIsICIiKSkgJT4lCiAgICAgIG11dGF0ZShnZW9fbmFtZSA9IHN0cl9yZXBsYWNlKGdlb19uYW1lLCAiIENpdHkgYW5kIEJvcm91Z2gkIiwgIiIpKSAlPiUKICAgICAgbXV0YXRlKGdlb19uYW1lID0gc3RyX3JlcGxhY2UoZ2VvX25hbWUsICIgQm9yb3VnaCQiLCAiIikpICU+JQogICAgICBtdXRhdGUoZ2VvX25hbWUgPSBjYXNlX3doZW4oCiAgICAgICAgZ2VvX25hbWUgPT0gIkJyb254IiB+ICJCcm9ueCBDb3VudHkiLAogICAgICAgIGdlb19uYW1lID09ICJLZW5haSBQZW5pbnN1bGEiIH4gIktlbmFpIFBlbmluc3VsYSBCb3JvdWdoIiwKICAgICAgICBnZW9fbmFtZSA9PSAiRG9uYSBBbmEgQ291bnR5IiB+ICJEb8OxYSBBbmEgQ291bnR5IiwKICAgICAgICBnZW9fbmFtZSA9PSAiV2FzaGluZ3RvbiwgRC5DLiIgfiAiRGlzdHJpY3Qgb2YgQ29sdW1iaWEiLAogICAgICAgIFRSVUUgfiBnZW9fbmFtZSkpICU+JQogICAgICBzZWxlY3QoLWNvdW50cnlfY29kZSwgLWFkbWluX2xldmVsLCAtZmlwcywgLXNhbXBsZXMpKSAlPiUKICAjIG5vcm1hbGl6ZSBnZW9fdHlwZSdzCiAgbXV0YXRlKGdlb190eXBlID0gY2FzZV93aGVuKAogICAgZ2VvX3R5cGUgJWluJSBjKCJjb3VudHJ5L3JlZ2lvbiIsICJjb3VudHJ5X3JlZ2lvbiIpIH4gImNvdW50cnkiLAogICAgZ2VvX3R5cGUgJWluJSBjKCJzdWJfcmVnaW9uXzEiLCAiYWRtaW4xIikgfiAic3RhdGVfcHJvdmluY2UiLAogICAgZ2VvX3R5cGUgJWluJSBjKCJzdWJfcmVnaW9uXzIiLCAiYWRtaW4yIiwgImNpdHkiKSB+ICJjb3VudHlfY2l0eSIsCiAgICBUUlVFIH4gIm5ld19nZW9fdHlwZSIpKSAlPiUKICBtdXRhdGUoZ2VvX3R5cGUgPSBpZl9lbHNlKGdlb19uYW1lID09ICJEaXN0cmljdCBvZiBDb2x1bWJpYSIsICJjb3VudHlfY2l0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9fdHlwZSkpCmBgYAoKIyBDb21wYXJpc29uIGFuZCB2YWxpZGF0aW9uCgojIyBHZW8gb3ZlcmxhcAoKYGBge3J9CmFsbF9tb2IgJT4lCiAgZ3JvdXBfYnkoZ2VvX3R5cGUsIGdlb19uYW1lKSAlPiUKICBzdW1tYXJpc2Uobl9zb3VyY2VzID0gbl9kaXN0aW5jdChzb3VyY2UpLAogICAgICAgICAgICAgc291cmNlcyA9IHN0cl9mbGF0dGVuKHVuaXF1ZShzb3VyY2UpLCBjb2xsYXBzZSA9ICIsIikpICU+JQogIGFycmFuZ2UoZGVzYyhuX3NvdXJjZXMpLCBnZW9fdHlwZSwgZ2VvX25hbWUpICU+JSBEVDo6ZGF0YXRhYmxlKCkKYGBgCgpUaGVyZSBhcmUgb25seSB0d28gbG9jYXRpb25zIHdoZXJlIGFsbCB0aHJlZSBkYXRhIHNvdXJjZXMgYWxpZ24gaW4gdGVybXMgb2YgbG9jYXRpb25zLgoKIyMgVGltZSBvdmVybGFwCgpgYGB7cn0KYWxsX21vYiAlPiUKICBzZWxlY3Qoc291cmNlLCBkYXRlKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSBkYXRlLCBmaWxsID0gc291cmNlKSkgKwogIGdlb21fYm94cGxvdCgpICsgY29vcmRfZmxpcCgpCmBgYAoKQXBwbGUgY292ZXJzIHRoZSBsYXJnZXN0IHJhbmdlIG9mIHRpbWUgYW5kIHN0YXJ0cyB0aGUgZWFybGllc3QuIERlc2NhcnRlcyBzdGFydHMgdGhlIGxhdGVzdCBidXQgYWxzbyBzZWVtcyB0byBoYXZlIHRoZSBtb3N0IHVwZGF0ZWQgZGF0YS4gIEdvb2dsZSBpcyBpbiB0aGUgbWlkZGxlIG9mIHRoZSBwYWNrLgoKIyMgR29vZ2xlL0FwcGxlL0Rlc2NhcnRlcwoKVGhlc2Ugc291cmNlcyBvdmVybGFwIGluIG9ubHkgdHdvIHBsYWNlcywgQmFsdGltb3JlIGFuZCBXYXNoaW5ndG9uIERDLgoKYGBge3J9CmFsbF9tb2IgJT4lCiAgZ3JvdXBfYnkoZ2VvX3R5cGUsIGdlb19uYW1lKSAlPiUKICBtdXRhdGUobl9zb3VyY2VzID0gbl9kaXN0aW5jdChzb3VyY2UpLAogICAgICAgICBzb3VyY2VzID0gc3RyX2ZsYXR0ZW4odW5pcXVlKHNvdXJjZSksIGNvbGxhcHNlID0gIiwiKSwKICAgICAgICAgd2VlayA9IGZsb29yX2RhdGUoZGF0ZSwgIndlZWsiKSkgJT4lCiAgZmlsdGVyKG5fc291cmNlcyA9PSAzICYgIW1ldHJpYyAlaW4lIGMoIm01MCIpKSAlPiUKICBncm91cF9ieSh3ZWVrLCBnZW9fbmFtZSwgbWV0cmljKSAlPiUKICBzdW1tYXJpc2UodmFsdWUgPSBtZWFuKHZhbHVlKSkgJT4lCiAgbXV0YXRlKG1ldHJpYyA9IHN0cl9yZW1vdmUobWV0cmljLCAiX3BjdF9jaGFuZ2VfZnJvbV9iYXNlbGluZSIpKSAlPiUKICBnZ3Bsb3QoYWVzKHdlZWssIHZhbHVlLCBjb2xvciA9IG1ldHJpYykpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKwogIGZhY2V0X3dyYXAofiBnZW9fbmFtZSwgbmNvbCA9IDEpICsKICBnZW9tX2xpbmUoKSAtPiBwCnBsb3RseTo6Z2dwbG90bHkocCkKYGBgCgojIyBHb29nbGUvQXBwbGUKCkFwcGxlL0dvb2dsZSBvdmVybGFwIGhhcHBlbnMgYXQgdGhlIGNvdW50cnkgbGV2ZWwuICBGdXJ0aGVybW9yZSwgd2Ugc2hvdWxkIGV4cGVjdCB0aGF0IEdvb2dsZSdzIHRyYW5zaXQgc3RhdGlvbiB2aXNpdHMgYWxpZ25zIHdlbGwgd2l0aCBBcHBsZSdzIHRyYW5zaXQgZGlyZWN0aW9uIHJlcXVlc3RzLgoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTh9CmFsbF9tb2IgJT4lCiAgZ3JvdXBfYnkoZ2VvX3R5cGUsIGdlb19uYW1lKSAlPiUKICBtdXRhdGUobl9zb3VyY2VzID0gbl9kaXN0aW5jdChzb3VyY2UpLAogICAgICAgICBzb3VyY2VzID0gc3RyX2ZsYXR0ZW4odW5pcXVlKHNvdXJjZSksIGNvbGxhcHNlID0gIiwiKSwKICAgICAgICAgd2VlayA9IGZsb29yX2RhdGUoZGF0ZSwgIndlZWsiKSkgJT4lCiAgZmlsdGVyKG5fc291cmNlcyA9PSAyICYgc291cmNlcyA9PSAiYXBwbGUsZ29vZ2xlIikgJT4lCiAgdW5pdGUobWV0cmljLCBzb3VyY2UsIG1ldHJpYykgJT4lCiAgZ3JvdXBfYnkod2VlaywgZ2VvX25hbWUsIG1ldHJpYykgJT4lCiAgc3VtbWFyaXNlKHZhbHVlID0gbWVhbih2YWx1ZSkpICU+JQogIGdncGxvdChhZXMod2VlaywgdmFsdWUsIGNvbG9yID0gZ2VvX25hbWUpKSArCiAgZmFjZXRfd3JhcCh+IG1ldHJpYywgbmNvbCA9IDIpICsKICBnZW9tX2xpbmUoKSAtPiBwCnBsb3RseTo6Z2dwbG90bHkocCkKYGBgCgojIyMgVHJhbnNpdCBjb3JyZWxhdGlvbnMKCmBgYHtyIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD02fQphbGxfbW9iICU+JQogIGZpbHRlcihtZXRyaWMgJWluJSBjKCJ0cmFuc2l0X3JvdXRpbmdfcmVxdWVzdHMiLAogICAgICAgICAgICAgICAgICAgICAgICJ0cmFuc2l0X3N0YXRpb25zIikpICU+JQogIGdyb3VwX2J5KGdlb19uYW1lLCBkYXRlKSAlPiUKICBmaWx0ZXIoYW55KHNvdXJjZSA9PSAiYXBwbGUiKSAmIGFueShzb3VyY2UgPT0gImdvb2dsZSIpKSAlPiUKICBhcnJhbmdlKGdlb190eXBlLCBnZW9fbmFtZSwgZGF0ZSkgJT4lCiAgZ2dwbG90KGFlcyhkYXRlLCB2YWx1ZSwgY29sb3IgPSBzb3VyY2UpKSArCiAgZ2VvbV9saW5lKCkgKyBmYWNldF93cmFwKH4gZ2VvX25hbWUsIG5jb2wgPSAzKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAoKYGBge3J9CmFsbF9tb2IgJT4lCiAgZmlsdGVyKG1ldHJpYyAlaW4lIGMoInRyYW5zaXRfcm91dGluZ19yZXF1ZXN0cyIsCiAgICAgICAgICAgICAgICAgICAgICAgInRyYW5zaXRfc3RhdGlvbnMiKSkgJT4lCiAgZ3JvdXBfYnkoZ2VvX25hbWUsIGRhdGUpICU+JQogIGZpbHRlcihhbnkoc291cmNlID09ICJhcHBsZSIpICYgYW55KHNvdXJjZSA9PSAiZ29vZ2xlIikpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBzZWxlY3QoLWdlb190eXBlLCAtbWV0cmljKSAlPiUKICBzcHJlYWQoc291cmNlLCB2YWx1ZSkgJT4lCiAgZ3JvdXBfYnkoZ2VvX25hbWUpICU+JQogIG11dGF0ZV9hdCh2YXJzKGFwcGxlLCBnb29nbGUpLCBzY2FsZSkgJT4lCiAgc3VtbWFyaXNlKGNvcnJlbGF0aW9uID0gY29yKGFwcGxlLCBnb29nbGUpKSAlPiUKICBtdXRhdGUoZ2VvX25hbWUgPSBpZl9lbHNlKGdlb19uYW1lICVpbiUgYygiVGFpd2FuIiwgIkphcGFuIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVGFpd2FuL0phcGFuIiwgIkFsbCBvdGhlciBjb3VudHJpZXMiKSkgJT4lCiAgYXJyYW5nZShjb3JyZWxhdGlvbikgJT4lCiAgZ2dwbG90KGFlcyhjb3JyZWxhdGlvbiwgZmlsbCA9IGdlb19uYW1lKSkgKyBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjIpCmBgYAoKTG9va3MgbGlrZSBjb3JyZWxhdGlvbnMgZG8gYmV0dGVyIGluIFdlc3Rlcm4gY291bnRyaWVzIHZzIEVhc3Rlcm4gY291bnRyaWVzIGJ1dCBnZW5lcmFsbHkgaGlnaCBjb3JyZWxhdGlvbnMgZXZlcnl3aGVyZSBleGNlcHQgZm9yIFRhaXdhbiBhbmQgSmFwYW4uCgojIyBHb29nbGUvRGVzY2FydGVzCgpUaGVyZSBhcmUgYSBsb3Qgb2YgY291bnRpZXMgc28gd2UgcmFuZG9tbHkgc2FtcGxlIDEwIGZvciBkaXNwbGF5LgoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTh9CmFsbF9tb2IgJT4lCiAgZ3JvdXBfYnkoZ2VvX3R5cGUsIGdlb19uYW1lKSAlPiUKICBmaWx0ZXIobl9kaXN0aW5jdChzb3VyY2UpID09IDIgJgogICAgICAgICAgIGFsbChzb3VyY2UgPT0gImdvb2dsZSIgfCBzb3VyY2UgPT0gImRlc2NhcnRlcyIpKSAlPiUKICB1bml0ZShtZXRyaWMsIHNvdXJjZSwgbWV0cmljKSAlPiUKICBncm91cF9ieSh3ZWVrID0gZmxvb3JfZGF0ZShkYXRlLCAid2VlayIpLCBnZW9fbmFtZSwgbWV0cmljKSAlPiUKICBzdW1tYXJpc2UodmFsdWUgPSBtZWFuKHZhbHVlKSkgJT4lCiAgZ3JvdXBfYnkoZ2VvX25hbWUpICU+JSBuZXN0KCkgJT4lIHVuZ3JvdXAoKSAlPiUKICBzYW1wbGVfbigxMCkgJT4lIHVubmVzdChjb2xzID0gYyhkYXRhKSkgJT4lCiAgZ2dwbG90KGFlcyh3ZWVrLCB2YWx1ZSwgY29sb3IgPSBtZXRyaWMpKSArCiAgZmFjZXRfd3JhcCh+IGdlb19uYW1lLCBuY29sID0gMikgKwogIGdlb21fbGluZSgpIC0+IHAKcGxvdGx5OjpnZ3Bsb3RseShwKQpgYGAKCldlIG5vdGUgdGhhdCBtNTAgaXMgYWN0dWFsbHkgYSBwb3NpdGl2ZSB2YWx1ZSBpbiBraWxvbWV0ZXJzIHNvIGRvZXNuJ3QgaGF2ZSB0aGUgc2FtZSAicGVyY2VudCBjaGFuZ2UgZnJvbSBiYXNlbGluZSIgdGhhdCBpcyB1c2VkIGluIG90aGVyIG1ldHJpY3MuCgojIyMgUmVzaWRlbnRpYWwgY29ycmVsYXRpb25zCgpTaW1pbGFyIHRvIHRoZSB0cmFuc2l0IGluZm9ybWF0aW9uIGJldHdlZW4gR29vZ2xlL0FwcGxlIC0gd2UgbG9vayBmb3Igc29tZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiBEZXNjYXJ0ZXMvR29vZ2xlIHJlc2lkZW50aWFsIHZzIG5vbi1yZXNpZGVudGlhbCBtb3ZlbWVudC4KCmBgYHtyfQphbGxfbW9iICU+JQogIGZpbHRlcihzb3VyY2UgJWluJSBjKCJnb29nbGUiLCAiZGVzY2FydGVzIikpICU+JQogIGZpbHRlcihnZW9fdHlwZSA9PSAiY291bnR5X2NpdHkiICYgbWV0cmljICE9ICJtNTAiKSAlPiUKICBtdXRhdGUobWV0cmljID0gaWZfZWxzZShtZXRyaWMgJWluJSBjKCJyZXNpZGVudGlhbCIsICJtNTBfaW5kZXgiLCAibTUwIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0cmljLCAibTUwX2luZGV4X2dvb2ciKSkgJT4lCiAgZ3JvdXBfYnkoZ2VvX3R5cGUsIGdlb19uYW1lLCBtZXRyaWMsIGRhdGUsIHNvdXJjZSkgJT4lCiAgc3VtbWFyaXNlKHZhbHVlID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgI211dGF0ZSh2YWx1ZSA9IGlmX2Vsc2UobWV0cmljID09ICJyZXNpZGVudGlhbCIsIHZhbHVlICogLTEsIHZhbHVlKSkgJT4lCiAgZ3JvdXBfYnkobWV0cmljLCBkYXRlKSAlPiUKICBzdW1tYXJpc2VfYXQodmFycyh2YWx1ZSksIG1lYW4sIG5hLnJtPVRSVUUpICU+JQogIGdncGxvdChhZXMoZGF0ZSwgdmFsdWUsIGNvbG9yID0gbWV0cmljKSkgKyBnZW9tX2xpbmUoKQpgYGAKCmBgYHtyfQphbGxfbW9iICU+JQogIGZpbHRlcihzb3VyY2UgJWluJSBjKCJnb29nbGUiLCAiZGVzY2FydGVzIikpICU+JQogIGZpbHRlcihnZW9fdHlwZSA9PSAiY291bnR5X2NpdHkiICYgbWV0cmljICE9ICJtNTAiKSAlPiUKICBtdXRhdGUobWV0cmljID0gaWZfZWxzZShtZXRyaWMgJWluJSBjKCJyZXNpZGVudGlhbCIsICJtNTBfaW5kZXgiLCAibTUwIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0cmljLCAibTUwX2luZGV4X2dvb2ciKSkgJT4lCiAgZ3JvdXBfYnkoZ2VvX3R5cGUsIGdlb19uYW1lLCBtZXRyaWMsIGRhdGUsIHNvdXJjZSkgJT4lCiAgc3VtbWFyaXNlKHZhbHVlID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lIHVuZ3JvdXAoKSAlPiUKICBzZWxlY3QoZ2VvX25hbWUsIG1ldHJpYywgZGF0ZSwgdmFsdWUpICU+JQogIHNwcmVhZChtZXRyaWMsIHZhbHVlKSAlPiUKICBncm91cF9ieShnZW9fbmFtZSkgJT4lCiAgZmlsdGVyKGFueSghaXMubmEobTUwX2luZGV4KSAmIGFueSghaXMubmEobTUwX2luZGV4X2dvb2cpKSkpICU+JQogIGdyb3VwX2J5KGdlb19uYW1lKSAlPiUKICBzdW1tYXJpc2UoY29ycmVsYXRpb25fbTUwID0gY29yKG01MF9pbmRleCwgbTUwX2luZGV4X2dvb2csCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIiksCiAgICAgICAgICAgIGNvcnJlbGF0aW9uX3JlcyA9IGNvcihtNTBfaW5kZXgsIHJlc2lkZW50aWFsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlID0gInBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpKSAlPiUKICBnYXRoZXIoY29ycmVsYXRpb25fdHlwZSwgY29ycmVsYXRpb24sIHN0YXJ0c193aXRoKCJjb3JyZWxhdGlvbl8iKSkgJT4lCiAgYXJyYW5nZSgtY29ycmVsYXRpb24pICU+JQogIGdncGxvdChhZXMoY29ycmVsYXRpb24sIGZpbGwgPSBjb3JyZWxhdGlvbl90eXBlKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuMSkKYGBgCgpUaGlzIGlzIGFsbCByZWFzc3VyaW5nLCBHb29nbGUgcmVzaWRlbnRpYWwgYWN0aXZpdHkgaXMgbmVnYXRpdmVseSBjb3JyZWxhdGVkIHdpdGggdGhlIGBtNTBfaW5kZXhgIGZyb20gRGVzY2FydGVzLiBUaGUgYXZlcmFnZSBvZiBhbGwgbm9uLXJlc2lkZW50aWFsIGFjdGl2aXRpZXMgZnJvbSBHb29nbGUgaXMgcG9zaXRpdmVseSBjb3JyZWxhdGVkIHRvIHRoZSBgbTUwX2luZGV4YC4gVGhlIHJlc2lkZW50aWFsIG5lZ2F0aXZlIGNvcnJlbGF0aW9uIGRvZXMgYXBwZWFyIHN0cm9uZ2VyIG92ZXIgdGhlc2UgZGlmZmVyZW50IHJlZ2lvbnMgdGhhbiBub24tcmVzaWRlbnRpYWwgR29vZ2xlIG1vYmlsaXR5IGRhdGEuCgojIEFnZ3JlZ2F0aW5nIGFuZCBjbGVhbmluZyBkYXRhCgpCZWxvdyB3ZSBhZ2dyZWdhdGUgZGF0YSBpbnRvIG1lYXN1cmVzIHRoYXQgcmVmbGVjdCBkZWNsaW5pbmcgbW9iaWxpdHkuIFRoaXMgbWVhbnMgYWdncmVnYXRpbmcgYWxsIHRoZSBBcHBsZSBtZXRyaWNzIGRpcmVjdGx5LCBhZ2dyZWdhdGluZyBhbGwgdGhlIEdvb2dsZSBtZXRyaWNzIHRyZWF0aW5nIHJlc2lkZW50aWFsIGFjdGl2aXR5IGludmVyc2VseSwgYW5kIHVzaW5nIHRoZSBub3JtYWxpemVkIERlc2NhcnRlcyBtZWFzdXJlIGRpcmVjdGx5LiBXZSBhbHNvIGludGVycG9sYXRlIGRhdGEgbWlzc2luZyBpbiB0aGUgR29vZ2xlIGRhdGEgZHVlIHRvIHByaXZhY3kgdGhyZXNob2xkcy4KCiMjIE1pc3NpbmduZXNzCgpgYGB7cn0KYWxsX21vYiAlPiUKICBncm91cF9ieShnZW9fdHlwZSwgbWV0cmljLCBzb3VyY2UpICU+JQogIHN1bW1hcmlzZShuYV9wY3QgPSBtZWFuKGlzLm5hKHZhbHVlKSkpICU+JQogIGFycmFuZ2UoZGVzYyhuYV9wY3QpKQpgYGAKCkdvb2dsZSBpcyBtaXNzaW5nIGEgZmFpciBiaXQgb2YgZGF0YSBpbiBpdHMgc3RhdHMgYWJvdXQgcGFya3MsIHRyYW5zaXRfc3RhdGlvbnMsIGFuZCByZXNpZGVudGlhbCAtIGVzcGVjaWFsbHkgYXQgdGhlIGNvdW50eSBsZXZlbC4gSXQncyBhIGxpdHRsZSBzdHJhbmdlIHRoYXQgdGhlcmUgaXMgbWlzc2luZyBkYXRhIGF0IHRoZSBzdGF0ZSBsZXZlbCBhbmQgdGhlIG9yZGVyIG9mIG1pc3NpbmduZXNzIGlzIHJldmVyc2VkIGZyb20gdGhlIGNvdW50eSBsZXZlbC4gVGhlcmUgaXMgdGhpcyBnZW5lcmFsIGlzc3VlIHdoZXJlIGRhdGEgaXMgY2Vuc29yZWQgd2hlbiB0aGVyZSBhcmUgdmVyeSBmZXcgcGVvcGxlIGluIGEgbG9jYXRpb24gb3IgbG9jYXRpb24gY2F0ZWdvcnkgLSBzbyBtYXliZSB0aGUgbWlzc2luZ25lc3MgaXMgaW5mb3JtYXRpb24gaW4gaXRzZWxmIC0gbGlrZSBhIGxvd2VyIGJvdW5kIG9uIHRoZSBhbW91bnQgb2YgYWN0aXZpdHkgaW4gYSBnaXZlbiBwbGFjZS4gVGhpcyBtaXNzaW5nbmVzcyBpcyBsaWtlbHkgdG8gY2F1c2Ugc29tZSBwcm9ibGVtcyBzbyB3ZSBzaG91bGQgY29tZSB1cCB3aXRoIGEgd2F5IHRvIGRlYWwgd2l0aCBpdC4KCldlIGNyZWF0ZSBgYWxsX21vYl9maXhgIHdoaWNoIGFkZHMgYW5vdGhlciBjb2x1bW4sIGB2YWx1ZV90eXBlYCB0aGF0IGhlbHBzIHVzIGRpc3Rpbmd1aXNoIGJldHdlZW4gdmFsdWVzIGludGVycG9sYXRlZCBieSBgbmEuYXBwcm94YCBhbmQgb3JpZ2luYWwgdmFsdWVzLiAgVGhlcmUgYXJlIG1hbnkgc3BvdHR5IHZhbHVlcywgZXNwZWNpYWxseSBhdCB0aGUgY291bnR5IGxldmVsLCBpbiB0aGUgR29vZ2xlIGRhdGEgc28gdGhpcyBpcyBuZWNlc3NhcnkgdG8gZG8gYW55dGhpbmcgdXNlZnVsLgoKIyMgRmlsbGluZyBhbmQgYWdncmVnYXRpbmcKCmBgYHtyfQooYWxsX21vYl9maXggPC0gYWxsX21vYiAlPiUKICAgIyBmaWxsIGluIG1pc3NpbmcgdmFsdWVzCiAgIGdyb3VwX2J5KG1ldHJpYywgZ2VvX3R5cGUsIGdlb19uYW1lLCBzb3VyY2UpICU+JQogICBtdXRhdGUodmFsdWVfYXBwcm94ID0gbmEuYXBwcm94KHZhbHVlLCBuYS5ybSA9IEZBTFNFKSkgJT4lCiAgIGdhdGhlcih2YWx1ZV90eXBlLCB2YWx1ZSwgdmFsdWUsIHZhbHVlX2FwcHJveCkgJT4lCiAgIHVuZ3JvdXAoKSAlPiUgCiAgICMgYWdncmVnYXRlIGdvb2dsZSBtb2JpbGl0eSBtZXRyaWNzCiAgIG11dGF0ZSh2YWx1ZSA9IGlmX2Vsc2UobWV0cmljID09ICJyZXNpZGVudGlhbCIsIHZhbHVlICogLTEsIHZhbHVlKSkgJT4lCiAgIG11dGF0ZShtZXRyaWMgPSBpZl9lbHNlKHNvdXJjZSA9PSAiZ29vZ2xlIiwgImdvb2dsZV9tb2IiLCBtZXRyaWMpKSAlPiUKICAgbXV0YXRlKG1ldHJpYyA9IGlmX2Vsc2Uoc291cmNlID09ICJhcHBsZSIsICJhcHBsZV9tb2IiLCBtZXRyaWMpKSAlPiUKICAgZmlsdGVyKG1ldHJpYyAhPSAibTUwIikgJT4lCiAgIG11dGF0ZShtZXRyaWMgPSBpZl9lbHNlKHNvdXJjZSA9PSAiZGVzY2FydGVzIiwgImRlc2NhcnRlc19tb2IiLCBtZXRyaWMpKSAlPiUKICAgZ3JvdXBfYnkobWV0cmljLCBnZW9fdHlwZSwgZ2VvX25hbWUsIHNvdXJjZSwgdmFsdWVfdHlwZSwgZGF0ZSkgJT4lCiAgIHN1bW1hcmlzZSh2YWx1ZSA9IG1lYW4odmFsdWUsIG5hLnJtID0gVFJVRSkpICU+JQogICB1bmdyb3VwKCkpICMlPiUKICAjIGdyb3VwX2J5KG1ldHJpYywgZ2VvX3R5cGUsIGdlb19uYW1lLCBzb3VyY2UpICU+JSBwdWxsKHBjdF92YWx1ZSkgJT4lIGhpc3QoKQogICMgZmlsdGVyKGFueShiZXR3ZWVuKHBjdF92YWx1ZSwgMC45NiwgMC45NykpKSAlPiUgCiAgIyBmaWx0ZXIocmJpbm9tKDEsIDEsIDAuMSkgPT0gMSkgJT4lCiAgIyBnZ3Bsb3QoYWVzKGRhdGUsIHZhbHVlKSkgKwogICMgZmFjZXRfZ3JpZChnZW9fbmFtZSB+IHZhbHVlX3R5cGUpICsKICAjIGdlb21fbGluZSgpCmBgYAoKIyBNb2JpbGl0eSBwYXJhbWV0ZXJzCgpXZSdkIGxpa2UgdG8gYmUgYWJsZSB0byBleHRyYWN0IGtleSBwYXJhbWV0ZXJzIGZyb20gdGhlIGFnZ3JlZ2F0ZWQgbW9iaWxpdHkgdGltZSBzZXJpZXMuICBTcGVjaWZpY2FsbHkgd2UncmUgaW50ZXJlc3RlZCBpbiAoMSkgd2hlbiB0aGUgZGVjbGluZXMgaW4gbW9iaWxpdHkgYmVnYW4sICgyKSBob3cgcXVpY2tseSBhbmQgYnkgaG93IG11Y2ggZGlkIG1vYmlsaXR5IGRlY2xpbmUsIGFuZCAoMykgaXMgdGhlcmUgYW55IGV2aWRlbmNlIG9mIGEgcmVvcGVuaW5nLgoKIyMgQ2hhbmdlIHBvaW50IGRldGVjdGlvbgoKVGhpcyBhbGdvcml0aG0gbG9va3MgZm9yIHNpZ25pZmljYW50IGNoYW5nZXMgaW4gdGltZSBzZXJpZXMgb3ZlciB0aW1lIGFuZCBoZWxwcyB1cyBmaW5kIHRoZSBwb2ludHMgaW4gdGltZSB3aGVuIHRoZXNlIGNoYW5nZXMgb2NjdXJyZWQuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpoZWxwX2VudmNwdCA8LSBmdW5jdGlvbihkZikgewogIGlmIChzdW0oIWlzLm5hKGRmJHZhbHVlKSkgPCAxNCkgewogICAgcmV0dXJuKE5BKSAKICB9IGVsc2UgewogICAgcmV0dXJuKGxpc3QoZW52Y3B0KG5hLmFwcHJveChkZiR2YWx1ZSksIHZlcmJvc2UgPSBGQUxTRSkpKQogIH0KfQoKbW9iX2NwdF9maWxlIDwtICJkYXRhL2FsbF9tb2JfY3B0LnJkcyIKCmlmICghZmlsZS5leGlzdHMobW9iX2NwdF9maWxlKSkgewogYWxsX21vYl9jcHQgPC0gYWxsX21vYl9maXggJT4lCiAgIGZpbHRlcih2YWx1ZV90eXBlID09ICJ2YWx1ZV9hcHByb3giKSAlPiUKICAgZ3JvdXBfYnkoZ2VvX3R5cGUsIGdlb19uYW1lLCBzb3VyY2UsIG1ldHJpYykgJT4lCiAgIG5lc3QoKSAlPiUgcm93d2lzZSgpICU+JQogICBtdXRhdGUoY3B0X21kbCA9IGhlbHBfZW52Y3B0KGRhdGEpKSAKIHNhdmVSRFMoYWxsX21vYl9jcHQsIG1vYl9jcHRfZmlsZSkKfSBlbHNlIHsKICBhbGxfbW9iX2NwdCA8LSByZWFkUkRTKG1vYl9jcHRfZmlsZSkKfQpgYGAKCmBgYHtyfQphbGxfbW9iX2NwdCAlPiUgcm93d2lzZSgpICU+JQogIG11dGF0ZShoYXNfbWRsID0gYWxsKCFpcy5uYShjcHRfbWRsKSkpICU+JQogIGdyb3VwX2J5KGdlb190eXBlLCBtZXRyaWMpICU+JQogIHN1bW1hcmlzZShoYXNfbWRsID0gbWVhbihoYXNfbWRsKSkgJT4lCiAgYXJyYW5nZShoYXNfbWRsKQpgYGAKCk1vc3QgbW9kZWxzIHdlcmUgYWJsZSB0byBydW4gd2l0aCB0aGUgZXhjZXB0aW9uIG9mIGEgZmV3LCBwcm9iYWJseSBkdWUgdG8gc3BhcnNlIGRhdGEuCgojIyBFeHRyYWN0IG1vYmlsaXR5IHBhcmFtZXRlcnMKCmBgYHtyfQpleHRyYWN0X21vYl9wYXJhbWV0ZXJzIDwtIGZ1bmN0aW9uKGNwdF9kYXRhLCBjcHRfbWRsLCBtZGxfbmFtZSA9ICJtZWFuY3B0IikgewogIGlmIChhbnkoaXMubmEoY3B0X21kbCkpKSB7CiAgICByZXR1cm4oTkEpCiAgfQogIGNwdF9tZGwgPC0gY3B0X21kbFtbbWRsX25hbWVdXQogIGRhdGVzIDwtIGNwdF9kYXRhICU+JSBwdWxsKGRhdGUpCiAgdmFsdWVzIDwtIGNwdF9kYXRhICU+JSBwdWxsKHZhbHVlKQogIAogIG1lYW5zIDwtIGNwdF9tZGxAcGFyYW0uZXN0JG1lYW4KICB2YXJzIDwtIGNwdF9tZGxAcGFyYW0uZXN0JHZhcmlhbmNlCiAgCiAgZW5kX3B0cyA8LSBjcHRfbWRsQGNwdHMKICBzZWdfbGVucyA8LSBzZWcubGVuKGNwdF9tZGwpCiAgc3RhcnRfcHRzIDwtIChlbmRfcHRzIC0gc2VnX2xlbnMpICsgMQogIAogIGVxX2lkeCA8LSBoZWFkKG9yZGVyKHNlZ19sZW5zLCBkZWNyZWFzaW5nID0gVCksIDIpCiAgbWluX2lkeCA8LSB3aGljaC5taW4obWVhbnMpCiAgbWF4X2lkeCA8LSB3aGljaC5tYXgobWVhbnMpCiAgZmlyc3RfaWR4IDwtIG1pbihtaW5faWR4LCBtYXhfaWR4KQogIGxhc3RfaWR4IDwtIG1heChtaW5faWR4LCBtYXhfaWR4KQogIAogICMgaW1wb3J0YW50IHZhbHMKICBjaGFuZ2Vfc3RhcnQgPC0gZW5kX3B0c1tmaXJzdF9pZHhdCiAgY2hhbmdlX2VuZCA8LSBzdGFydF9wdHNbbGFzdF9pZHhdCiAgCiAgIyBzYXZlIHZhbHMKICBjaGFuZ2Vfc3RhcnRfZGF0ZSA8LSBkYXRlc1tjaGFuZ2Vfc3RhcnRdCiAgY2hhbmdlX2VuZF9kYXRlIDwtIGRhdGVzW2NoYW5nZV9lbmRdCiAgCiAgbWVhbl9iZWZvcmUgPC0gbWVhbnNbZmlyc3RfaWR4XQogIG1lYW5fYWZ0ZXIgPC0gbWVhbnNbbGFzdF9pZHhdCiAgdmFyX2JlZm9yZSA8LSB2YXJzW2ZpcnN0X2lkeF0KICB2YXJfYWZ0ZXIgPC0gdmFyc1tsYXN0X2lkeF0KICBjaGFuZ2VfZGlmZiA8LSBtZWFuX2FmdGVyIC0gbWVhbl9iZWZvcmUKICBjaGFuZ2Vfc2xvcGUgPC0gY2hhbmdlX2RpZmYgLyAobGFzdF9pZHggLSBmaXJzdF9pZHgpCiAgc3RhcnRfc2VnIDwtIGMoc3RhcnRfcHRzW2ZpcnN0X2lkeF0sIGVuZF9wdHNbZmlyc3RfaWR4XSkKICBzdGFydF9zZWdfZGF0ZXMgPC0gZGF0ZXNbc3RhcnRfc2VnXQogIGVuZF9zZWcgPC0gYyhzdGFydF9wdHNbbGFzdF9pZHhdLCBlbmRfcHRzW2xhc3RfaWR4XSkKICBlbmRfc2VnX2RhdGVzIDwtIGRhdGVzW2VuZF9zZWddCiAgcmV2ZXJ0IDwtIGxhc3QobWVhbnMpIC0gbWVhbl9hZnRlcgogIAogIGRhdGEuZnJhbWUoCiAgICBjaGFuZ2Vfc3RhcnRfZGF0ZSA9IGNoYW5nZV9zdGFydF9kYXRlLAogICAgY2hhbmdlX2RpZmYgPSBjaGFuZ2VfZGlmZiwKICAgIGNoYW5nZV9zbG9wZSA9IGNoYW5nZV9zbG9wZSwKICAgIGNoYW5nZV9lbmRfZGF0ZSA9IGNoYW5nZV9lbmRfZGF0ZSwKICAgIGNoYW5nZV9kYXlzID0gY2hhbmdlX2VuZF9kYXRlIC0gY2hhbmdlX3N0YXJ0X2RhdGUsCiAgICBtZWFuX2JlZm9yZSA9IG1lYW5fYmVmb3JlLAogICAgbWVhbl9hZnRlcj0gbWVhbl9hZnRlciwKICAgIHZhcl9iZWZvcmUgPSB2YXJfYmVmb3JlLAogICAgdmFyX2FmdGVyPSB2YXJfYWZ0ZXIsCiAgICBzZWdfc3RhcnRfZGF0ZSA9IGRhdGVzW3N0YXJ0X3B0c1tmaXJzdF9pZHhdXSwKICAgIHNlZ19lbmRfZGF0ZSA9IGRhdGVzW2VuZF9wdHNbbGFzdF9pZHhdXSwKICAgIHJldmVydCA9IHJldmVydCkKfQoKYWxsX21vYl9wYXJhbXMgPC0gYWxsX21vYl9jcHQgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIGNwdF9wYXJhbXMgPSBsaXN0KGV4dHJhY3RfbW9iX3BhcmFtZXRlcnMoZGF0YSwgY3B0X21kbCkpKSAlPiUKICB1bm5lc3QoY3B0X3BhcmFtcykgJT4lIHVuZ3JvdXAoKQpgYGAKCmBgYHtyfQpwbG90X3JvdyA8LSBmdW5jdGlvbih4KSB7CiAgeCAlPiUgcGx1Y2soImNwdF9tZGwiLCAxKSAlPiUgLltbIm1lYW5jcHQiXV0gJT4lIHBsb3QoKQp9CgphbGxfbW9iX3BhcmFtcyAlPiUKICBhcnJhbmdlKGNoYW5nZV9zbG9wZSkgJT4lCiAgc2VsZWN0KHNvdXJjZSwgZ2VvX25hbWUsIGNoYW5nZV9zbG9wZSkgJT4lCiAgaGVhZCgxMDApICU+JQogIERUOjpkYXRhdGFibGUoKQpgYGAKClRoZSBmaXJzdCB0aGluZyB0aGF0IHBvcHMgb3V0IGFyZSBEZXNjYXJ0ZXMgbnVtYmVycyBhcmUgd2lsZCAoYW5kIGxpa2VseSB2ZXJ5IG5vaXN5KS4gIFdlIHNob3VsZCBwcm9iYWJseSBpZ25vcmUgdGhlbS4KCmBgYHtyfQphbGxfbW9iX3BhcmFtcyAlPiUKICBmaWx0ZXIoc291cmNlICE9ICJkZXNjYXJ0ZXMiKSAlPiUKICBhcnJhbmdlKGNoYW5nZV9zbG9wZSkgJT4lCiAgc2VsZWN0KHNvdXJjZSwgZ2VvX25hbWUsIGNoYW5nZV9zbG9wZSkgJT4lCiAgaGVhZCgxMDApICU+JQogIERUOjpkYXRhdGFibGUoKQpgYGAKCkFwcGxlIGFuZCBHb29nbGUgY2hhbmdlIGZyb20gYmFzZWxpbmUgbWV0cmljcyBsb29rIHJlYXNvbmFibGUuICBXZSdsbCB1c2UgR29vZ2xlIGZvciBhIHJlZ2lvbiBsZXZlbCBhbmFseXNpcyBhbmQgQXBwbGUrR29vZ2xlIGZvciBjb3VudHJ5IGxldmVsIGNoYW5nZXMuCgpgYGB7cn0KYWxsX21vYl9wYXJhbXMgJT4lCiAgZmlsdGVyKGdlb190eXBlID09ICJjb3VudHJ5IikgJT4lCiAgc2VsZWN0KHNvdXJjZSwgZ2VvX25hbWUsIGNoYW5nZV9zdGFydF9kYXRlKSAlPiUKICBzcHJlYWQoc291cmNlLCBjaGFuZ2Vfc3RhcnRfZGF0ZSkgJT4lCiAgZ2dwbG90KGFlcyhnb29nbGUsIGFwcGxlKSkgKyBnZW9tX3BvaW50KCkKYGBgCgpNb3N0IG9mIHRoZSBjaGFuZ2UgZGF0ZXMgaW4gbWFyY2ggYXJlIGluIGFncmVlbWVudCAtIGJ1dCBzb21lIGVhcmxpZXIgY2hhbmdlIGRhdGVzIHBpY2tlZCB1cCBieSBBcHBsZSBhcmVuJ3QgcGlja2VkIHVwIGJ5IEdvb2dsZSBiZWNhdXNlIHRoZSBkYXRhIHNldCBzdGFydGVkIGxhdGVyIHRoYW4gQXBwbGUuCgpgYGB7cn0KYWxsX21vYl9wYXJhbXMgJT4lCiAgZmlsdGVyKGdlb190eXBlID09ICJjb3VudHJ5IikgJT4lCiAgc2VsZWN0KHNvdXJjZSwgZ2VvX25hbWUsIGNoYW5nZV9zdGFydF9kYXRlKSAlPiUKICBzcHJlYWQoc291cmNlLCBjaGFuZ2Vfc3RhcnRfZGF0ZSkgJT4lCiAgbXV0YXRlKGRpZmYgPSBhYnMoZ29vZ2xlIC0gYXBwbGUpKSAlPiUKICBhcnJhbmdlKGRlc2MoZGlmZikpCmBgYAoKSGVyZSB3ZSBzZWUgdGhhdCBtYW55IEFzaWEgcmVnaW9uIGNvdW50cmllcyBhbmQgY291bnRyaWVzIHRoYXQgc3RhcnRlZCB0aGVpciBzaHV0ZG93biBlYXJsaWVyIHdlcmUgcGlja2VkIHVwIGluIHRoZSBBcHBsZSBkYXRhIGVhcmxpZXIuIEZvciBjb3VudHJpZXMgdGhhdCBzdGFydGVkIHRoZWlyIHNodXRkb3duIGxhdGVyIGluIE1hcmNoIHRoZXJlIHdhc24ndCBhcyBtdWNoIG9mIGEgZGlmZmVyZW5jZSAtIHdoaWNoIGlzIHByb21pc2luZyBmb3Igb3VyIGNoYW5nZSBwb2ludCBkZXRlY3RvbiBtb2RlbC4KCmBgYHtyfQphbGxfbW9iX3BhcmFtcyAlPiUKICBmaWx0ZXIoZ2VvX25hbWUgPT0gIkhvbmcgS29uZyIpICU+JSBzbGljZSgxKSAlPiUgcGxvdF9yb3coKQoKYWxsX21vYl9wYXJhbXMgJT4lCiAgZmlsdGVyKGdlb19uYW1lID09ICJIb25nIEtvbmciKSAlPiUgc2xpY2UoMikgJT4lIHBsb3Rfcm93KCkKYGBgCgpHb29nbGUgbG9va3MgbGlrZSB0aGV5IGFyZSBub3QgZ2V0dGluZyB0aGUgY29ycmVjdCBzaHV0ZG93biBkYXRlcyBmb3IgZWFybHkgaW1wbGVtZW50ZXNyIGJlY2F1c2UgdGhlIHRpbWUgc2VyaWVzIG9ubHkgYmVnaW5zIGluIE1hcmNoIHdoaWxlIEFwcGxlJ3Mgc3RhcnQgaW4gQXByaWwuICBBdCBsZWFzdCBmb3IgdGhlIGxhdGUgaW1wbGVtZW50ZXJzLCB0aGUgbnVtYmVycyBzZWVtIHRvIGFsaWduIHJlYXNvbmFibHkgd2VsbC4gIFRoaXMgdGVsbHMgdXMgdGhhdCB1c2luZyBHb29nbGUgZGF0YSBmb3IgYW4gaW50ZXJuYXRpb25hbCBjb21wYXJpc29uIG1heSBub3QgYmUgdXNlZnVsIGJlY2F1c2UgY291bnRyaWVzIGRpZG4ndCBzdGFydCB1bnRpbCBlYXJsaWVyLiBGb3IgY291bnRyaWVzIGxpa2UgSXRhbHksIHdobyBzdGFydGVkIGxhdGVyIChzYWRseSksIHdlIGFyZSBhYmxlIHRvIHNlZSB0aGUgY2hhbmdlIHBvaW50cyBhbmQgaXQgbG9va3MgbGlrZSB3ZSdyZSBhYmxlIHRvIHBpY2sgdXAgdGhlIHN0YXJ0IGRhdGUgY29ycmVjdGx5LgoKYGBge3J9CmFsbF9tb2JfcGFyYW1zICU+JQogIGZpbHRlcihnZW9fbmFtZSA9PSAiSXRhbHkiKSAlPiUgc2xpY2UoMSkgJT4lIHBsb3Rfcm93KCkKCmFsbF9tb2JfcGFyYW1zICU+JQogIGZpbHRlcihnZW9fbmFtZSA9PSAiSXRhbHkiKSAlPiUgc2xpY2UoMikgJT4lIHBsb3Rfcm93KCkKYGBgCgo8IS0tICMgQ292aWQgZGF0YSAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIHNvdXJjZSgiY292aWRjb21wX2xpYi5SIikgLS0+CjwhLS0gamh1IDwtIGZldGNoUHJlcEpodURhdGEoKSAtLT4KPCEtLSBjb3Z0cmFjayA8LSBmZXRjaFByZXBDb3ZUcmFja0RhdGEoKSAtLT4KPCEtLSBueXQgPC0gZmV0Y2hQcmVwTnl0KCkgLS0+CjwhLS0gY2RzIDwtIGZldGNoUHJlcENvckRhdGFTY3JhcGUoKSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBkZWF0aHMgPC0gYmluZF9yb3dzKCAtLT4KPCEtLSAgIGpodSAlPiUgLS0+CjwhLS0gICAgIHJlbmFtZShnZW9fbmFtZSA9IGNvdW50cnkpICU+JSAtLT4KPCEtLSAgICAgbXV0YXRlKGdlb190eXBlID0gImNvdW50cnkiKSwgLS0+CjwhLS0gICBjb3Z0cmFjayAlPiUgLS0+CjwhLS0gICAgIHJlbmFtZShnZW9fbmFtZSA9IHN0YXRlKSAlPiUgLS0+CjwhLS0gICAgIG11dGF0ZShnZW9fdHlwZSA9ICJzdGF0ZV9wcm92aW5jZSIpLCAtLT4KPCEtLSAgIG55dCAlPiUgLS0+CjwhLS0gICAgIHJlbmFtZShnZW9fbmFtZSA9IGNvdW50eSkgJT4lIC0tPgo8IS0tICAgICBtdXRhdGUoZ2VvX3R5cGUgPSAiY291bnR5X2NpdHkiKSwgLS0+CjwhLS0gICBjZHMgJT4lIC0tPgo8IS0tICAgICByZW5hbWUoZ2VvX25hbWUgPSBsb2NhdGlvbikgJT4lIC0tPgo8IS0tICAgICBtdXRhdGUoZ2VvX3R5cGUgPSAic3RhdGVfcHJvdmluY2UiKSkgJT4lIC0tPgo8IS0tICAgZmlsdGVyKHN0YXQgPT0gImRlYXRocyIpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShnZW9fbmFtZSA9IGNhc2Vfd2hlbiggLS0+CjwhLS0gICAgIGdlb19uYW1lID09ICJLb3JlYSwgU291dGgiIH4gIlNvdXRoIEtvcmVhIiwgLS0+CjwhLS0gICAgIGdlb19uYW1lID09ICJVUyIgfiAiVW5pdGVkIFN0YXRlcyIsIC0tPgo8IS0tICAgICBnZW9fbmFtZSA9PSAiVGFpd2FuKiIgfiAiVGFpd2FuIiwgLS0+CjwhLS0gICAgIGdlb19uYW1lID09ICJNeWFubWFyIChCdXJtYSkiIH4gIkJ1cm1hIiwgLS0+CjwhLS0gICAgIGdlb19uYW1lID09ICJOZXcgWW9yayBDaXR5LCBOZXcgWW9yayIgfiAiTmV3IFlvcmsgQ2l0eSIsIC0tPgo8IS0tICAgICBzdHJfZW5kcyhnZW9fbmFtZSwgIiwgVW5pdGVkIFN0YXRlcyIpIH4gc3RyX3JlbW92ZShnZW9fbmFtZSwgIiwgVW5pdGVkIFN0YXRlcyIpLCAtLT4KPCEtLSAgICAgVFJVRSB+IGdlb19uYW1lKSkgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gYWxsX21vYl9wYXJhbXMgJT4lIC0tPgo8IS0tICAgZmlsdGVyKHNvdXJjZSAhPSAiZGVzY2FydGVzIiAmIHNvdXJjZSAhPSAiYXBwbGUiKSAlPiUgLS0+CjwhLS0gICBzZWxlY3QoLWNwdF9tZGwpICU+JSAtLT4KPCEtLSAgIHVubmVzdChkYXRhKSAlPiUgIC0tPgo8IS0tICAgbGVmdF9qb2luKGRlYXRocywgYnkgPSBjKCJnZW9fbmFtZSIsICJkYXRlIiksIC0tPgo8IS0tICAgICAgICAgICAgIHN1ZmZpeCA9IGMoIiIsICIuZGVhdGhzIikpICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KGdlb19uYW1lLCBnZW9fdHlwZSkgJT4lIC0tPgo8IS0tICAgZmlsdGVyKGFsbChpcy5uYShzdGF0KSkpICU+JSAtLT4KPCEtLSAgIHNlbGVjdChnZW9fbmFtZSwgZ2VvX3R5cGUpICU+JSB1bmlxdWUoKSAtLT4KPCEtLSBgYGAgLS0+CiMgQXBwZW5kaXgKCg==